/**
	License:		Artistic license 2.0 (see license.txt, also available online at http://www.perlfoundation.org/artistic_license_2_0)
	Authors:		OrbitalLab (http://www.orbitallab.ru/moonglide/), 2007-2009

	File:			EngineCore.d
	Description:	moonglide engine core, orginizes and runs all subsystems
	Date:			11.04.2008 by Digited
**/
module moonglide.core.EngineCore;

// set vertsions to enable one or more videodrivers
// VideoDriver_OpenGL, VideoDriver_Direct3D9, VideoDriver_Direct3D10
version = VideoDriver_OpenGL;

public import
	tango.time.StopWatch,
	tango.core.Memory,

	moonglide.math.MoonMath,
	moonglide.os.IOS,
	moonglide.scenes.IScene,
	moonglide.scenes.SceneManager,

	moonglide.core.Controllers,
	moonglide.core.Events,
	moonglide.core.UploadManager,

	moonglide.io.LogSystem,
	moonglide.io.Config,

	moonglide.renderer.IRenderer,
	moonglide.resources.Management;

version( VideoDriver_OpenGL )			import moonglide.renderer.opengl.OpenGLRenderer;
else version( VideoDriver_Direct3D )	import moonglide.renderer.direct3d9.Direct3D9Renderer;

version( Windows )						import moonglide.os.win32.Win32OS;
else version( linux )					import moonglide.os.linux.LinuxOS;

const float CAPTION_CHANGE_TIME = 2000.f;
const float GC_COLLECT_TIME		= 30000.f;

//===============================================================
/// class CEngineCore - moonglide engine core, keeps and rules all subsystems
//===============================================================
class CEngineCore : IEventListener, IEventGenerator
{
	mixin TMoonGlideObject;
	mixin TEventGenerator;

	protected:
		static CEngineCore _singleton;

		this() {}

		IOS				_osmodule;
		IRenderer		_renderer;
		CSceneManager	_scenemanager;
		CResManager		_resmanager;
		CUploadManager	_uploadmanager;

		CConfig			_config;

		bool			_shutdownDone,
						_running,
						_programFileSystemCreated;		// CMoonFile.drivers[ "program" ] from application path

		char[]			_windowCaption;

		void _RunConsoleInterpreterSession()
		{
			void _PrintHelp()
			{
				Cout( "\ndebug reports: core.report, scene.report, res.report, rend.report\n" ).newline;
			}

			bool exit = false;
			char[] command;
			Cout( "\nInterpreter mode, engine is freezed. Print \"help\" to get it or \"go\" to continue rendering\n" ).newline;

			while( !exit )
			{
				Cout( ">>> " ).flush;
				command = Cin.get();

				switch( command )
				{
					case "go":
						Cout( "\nok, continue rendering...\n" ).newline;
						exit = true;
						break;

					case "help":
						_PrintHelp;
						break;

					case "core.report":
						debug DbgReport;
						break;

					case "scene.report":
						debug _scenemanager.DbgReport;
						break;

					case "res.report":
						debug _resmanager.DbgReport;
						break;

					default:
						Cout( "Do what?.. O_o" ).newline;
						break;
				}
			}
		}

	public:
		static CEngineCore opCall()
		{
			if( _singleton is null )
				_singleton = new CEngineCore;

			return _singleton;
		}

		~this() { shutdown(); }

		IRenderer		renderer()		{ return _renderer; }
		CSceneManager	sceneManager()	{ return _scenemanager; }
		CResManager		resManager()	{ return _resmanager; }
		CUploadManager	uploadManager()	{ return _uploadmanager; }


		//===============================================================
		/// Engine start with creation of all subsystems and initialization
		/// NOTE: if you keep "checkConfigFile" true, core will search for file "configFileName" and launch from it
		//===============================================================
		bool Launch(
			char[]	applicationPath,
			char[]	windowName		= "MoonGlide application",
			bool	checkConfigFile	= true,
			char[]	configFileName	= "engine.cfg",
			ubyte	driverType 		= 0,						// DRIVERTYPE.DT_OPENGL,
			Size2ui	windowSize 		= Size2ui( 800, 600 ),
			ubyte	bpp				= 24,
			bool 	antialiasing	= true,
			bool 	stencil 		= false,
			bool	fullscreen 		= false,
			ubyte	refreshFreq		= 60,
			bool 	vsync 			= false,
			char[]	startScene		= "" )
		in
		{
			moonAssert( applicationPath.length > 0, "application path is empty", __FILE__, __LINE__ );

			if( !checkConfigFile )
			{
				moonAssert( driverType <= DRIVERTYPE.max, "driver type out of DRIVERTYPE enum range", __FILE__, __LINE__ );
				moonAssert(( windowSize.x != 0 ) && ( windowSize.y != 0 ), "window size equals zero", __FILE__, __LINE__ );
				moonAssert(( bpp == 16 ) || ( bpp == 24 ) || ( bpp == 32 ), "bpp seems to be wrong", __FILE__, __LINE__ );
				moonAssert(( refreshFreq >= 60 ) && ( refreshFreq <= 120 ), "strange refresh frequency", __FILE__, __LINE__ );
			}
		}
		body
		{
			_windowCaption = windowName.dup;

			static char[] folderPath;
			static char sep = '/';

			if(( "program" in CMoonFile.drivers ) is null )
			{
				uint lastPos = 0; // remove exe file from program path (once at engine start)

				foreach( index, ch; applicationPath )
					if( ch == sep )
						lastPos = index;

				if( lastPos == 0 )
					errorLog( "could not parse application path!" );
				else
					folderPath = applicationPath[0..lastPos];

				auto appFolder = new FileFolder( folderPath );
				CMoonFile().mount( appFolder, "program" );
				CMoonFile.drivers[ "program" ] = appFolder;
			}

			if( checkConfigFile )
			{
				_config = CConfig( configFileName );

				// recursion with Launch parameters from config file
				return Launch(
					folderPath,
					_config.getString( "windowcaption", "MoonGlide engine" ),		// window caption
					false,															// do NOT check for config this time
					"",																// no config this time
					_config.getInteger( "drivertype", 0 ),							// driver type: 0 == DT_OPENGL, 1 == DT_D3D9
					Size2ui(
						_config.getInteger( "width", 800 ),
						_config.getInteger( "height", 600 )),						// window size
					_config.getInteger( "bpp", 32 ),								// bits per pixel
					_config.getBool( "antialiacing", true ),						// antialiasing
					_config.getBool( "stencil", true ),								// stencil buffer
					_config.getBool( "fullscreen", false ),							// fullscreen
					_config.getInteger( "refreshrate", 60 ),						// monitor refresh frequency (Hz)
					_config.getBool( "vsync", false ),								// vertical syncronisation (windows only)
					_config.getString( "start_scene", "" ));						// start scene name. If empty, list of registered scenes will be printed with choice
			}

			version( VideoDriver_OpenGL )			_renderer = COpenGLRenderer();
			else version( VideoDriver_Direct3D )	_renderer = CDirect3D9Renderer();

			version( Windows )		_osmodule = CWin32OS();
			else version( linux )	_osmodule = CLinuxOS();

			_scenemanager 	= CSceneManager();
			_resmanager		= CResManager();
			_uploadmanager	= CUploadManager();

			_resmanager.registerListener( _uploadmanager, eeventtype.resource );
			_osmodule.registerListener( this );

			debug
			{
				moonAssert( _renderer !is null, "renderer is null", __FILE__, __LINE__ );
				moonAssert( _osmodule !is null, "os module is null", __FILE__, __LINE__ );
				moonAssert( _scenemanager !is null, "scene manager is null", __FILE__, __LINE__ );
			}

			if( _renderer.initialize( _osmodule, windowName, windowSize, bpp, refreshFreq,
					fullscreen, vsync, antialiasing, stencil ))
			{
				Cout( "ok" ).newline;

				while( 1 )
				{
					// check start scene
					if( startScene.length )
						if( _scenemanager.sceneExists( startScene ))
							if( _scenemanager.setActiveScene( startScene ))
								if( _scenemanager.getActiveScene().initialize() )
									return true;
								else
									errorLog( "scene initialization failed" );
							else
								errorLog( "could not start scene " ~ startScene );
						else
							errorLog( "scene [" ~ startScene ~ "] is not registered" );

					char[][] scenes = _scenemanager.scenes;

					if( scenes.length )
					{
						Cout( "Available scenes:" ).newline;

						foreach( i, name; scenes )
							Cout( Format( "{}. {}", i, name )).newline;

						Cout( "Choose one by index/name or quit: >>> " ).flush;
						startScene = Cin.get();

						if( startScene.length == 0 || startScene == "quit" || startScene == "q" )
						{
							Cout( "Bye bye! ^___^" ).newline;
							break;
						}

						try
						{
							int maybeIndex = tango.text.convert.Integer.toInt( startScene );

							if( maybeIndex >= 0 && maybeIndex < scenes.length )
								startScene = scenes[maybeIndex];
							else
							{
								Cout( "Don't have it... See ya! ^___^" ).newline;
								break;
							}
						}
						catch( Exception e ) {}
					}
					else
					{
						errorLog( "no scenes registered" );
						break;
					}
				}
			}
			else
				errorLog( "could not initialize renderer" );

			return false;
		}

		//===============================================================
		/// Start of rendering loop, including system loop
		//===============================================================
		void run()
		{
			_running = true;

			StopWatch timer;
			timer.start;

			float lasttime = 0, frametime = 0, showcounter = 0, gc_counter = 0;

			debug debugLogG( "Engine core is going to begin main cycle" );

			while( _osmodule.runLoop )
			{
				if( _running )
				{
					frametime = cast(float)timer.microsec / 1000.0f - lasttime;
					lasttime += frametime;
					_renderer.fpsCounter.addFrameTime( frametime );

					showcounter += frametime;

					if( showcounter >= CAPTION_CHANGE_TIME )
					{
						static char[] newCaption;
						newCaption = Format( "{} :: {} fps", _windowCaption, _renderer.fpsCounter.fps );
						_osmodule.setWindowCaption( newCaption, false );

						showcounter -= CAPTION_CHANGE_TIME;
					}

	//				if( gc_counter >= GC_COLLECT_TIME )
	//				{
	//					GC.collect;
	//					GC.minimize;
	//					gc_counter -= GC_COLLECT_TIME;
	//				}

					frametime /= 1000.f;

					_uploadmanager.update;

					_osmodule.checkKeyStates;		// check key states just before scene run
					_scenemanager.processActiveScene( frametime );
					mouseState.cleanStats;
				}
				else
					debug
					{
						if( _scenemanager.scenes().length > 1 )
						{
							_scenemanager.getActiveScene().shutdown();
							Cout( "\n\t Ok, scene down. Want some more?\n" ).newline;
							char[][] scenes = _scenemanager.scenes;
							char[] startScene;

							while( 1 )
							{
								Cout( "Available scenes:" ).newline;

								foreach( i, name; scenes )
									Cout( Format( "{}. {}", i, name )).newline;

								Cout( "Choose one by index/name or quit: >>> " ).flush;
								startScene = Cin.get();

								if( startScene.length == 0 || startScene == "quit" || startScene == "q" )
								{
									Cout( "Bye bye! ^___^" ).newline;
									break;
								}

								try
								{
									int maybeIndex = tango.text.convert.Integer.toInt( startScene );

									if( maybeIndex >= 0 && maybeIndex < scenes.length )
										startScene = scenes[maybeIndex];
									else
									{
										Cout( "Don't have it... See ya! ^___^" ).newline;
										break;
									}
								}
								catch( Exception e ) {}

								if( _scenemanager.setActiveScene( startScene ))
									if( _scenemanager.getActiveScene().initialize() )
									{
										_running = true;
										break;
									}
									else
										errorLog( "scene initialization failed" );
								else
									errorLog( "could not start scene " ~ startScene );
							}

							if( !_running )
								break;
						}
					}
					else
						break;
			}

			traceLog.info( Format( "\n\nTotal rendered frames: {}\nTotal rendering time: {} seconds\n\n",
				_renderer.fpsCounter.getTotalFrames, cast(float)timer.microsec / 1000000.0f ));

			timer.stop; // when * 0.000001, it returns always 0.00... very strange!
		}

		//===============================================================
		/// Stop and clean stuff
		//===============================================================
		void shutdown()
		{
			if( _shutdownDone )
				return;

			_shutdownDone = true;

			debug debugLogB( "Engine shutdown started..." );

			// post exit event
			CEvent event = new CEvent( eeventtype.program, eprogramevent.close );
			generateEvent( event );

			debug CSceneManager().DbgReport();
			_scenemanager.clearAllScenes;

			_resmanager.destroyAllResources;
			debug _resmanager.DbgReport;
			_resmanager.unRegisterListenerAll( _uploadmanager );

			try GC.collect;
			catch( Exception e )
				errorLog( "core shutdown: GC.collect 1 throwed an error: " ~ e.msg );

            _renderer.deInitialize;
			_osmodule.shutdown;
			_osmodule.unRegisterListener;

			try GC.collect;
			catch( Exception e )
				errorLog( "core shutdown: GC.collect 2 throwed an error: " ~ e.msg );

			delete _uploadmanager;
			delete _scenemanager;
			delete _resmanager;
			delete _renderer;
			delete _osmodule;

			try GC.collect;
			catch( Exception e )
				errorLog( "core shutdown: GC.collect 3 throwed an error: " ~ e.msg );

			GC.minimize;

			debug debugLogG( "Engine core shutdown done" );
		}

		//============================================================
		/// onEventCallback - gets events from subscribed senders
		//============================================================
		void onEventCallback( CEvent event, IEventGenerator sender )
		{
			switch( event.type )
			{
				case eeventtype.program:
					switch( event.subtype )
					{
						case eprogramevent.command:
							if( "freeze" in event.bools )
								_osmodule.freezeCursor( event.bools[ "freeze" ] );
							return;
					}
					break;
			}
		}

		//===============================================================
		/// event callback - recieves all system input from os module as a delegate
		//===============================================================
		void onEventReceive( CEvent event, IEventSender sender )
		{
			switch( event.type )
			{
				case eeventtype.program:
					switch( event.subtype )
					{
						case eprogramevent.resize:
							_renderer.viewport = Size2ui( event.ints["size_x"], event.ints["size_y"] );
							generateEvent( event );
							return;

						case eprogramevent.command:
							debug debugLog.trace( "engCore is freezing cursor" );

							if( "freeze" in event.bools )
								_osmodule.freezeCursor( event.bools[ "freeze" ] );
							return;
					}
					break;

					case eeventtype.system:
						switch( event.subtype )
						{
							case esystemevent.kbkey:
								if( event.ints[ "kb_key" ] == _KB_F12 && event.bools[ "kb_pressed" ] == true )
									_renderer.saveScreenshot();

								if( event.ints[ "kb_key" ] == _KB_ESCAPE && event.bools[ "kb_pressed" ] == true )
									_running = false;

								if( event.ints[ "kb_key" ] == _KB_TAB && event.bools[ "kb_pressed" ] == true )
									if( keyboardState[_KB_CONTROL] && keyboardState[_KB_SHIFT] )
									{
										_RunConsoleInterpreterSession();
										break;
									}

								debug
								{
                                    if(( event.ints[ "kb_key" ] == _KB_F1 ) && ( event.bools[ "kb_pressed" ] == true ))
								        CEngineCore().DbgReport;
								}

								keyboardState( event.ints["kb_key"], event.bools["kb_pressed"] );

								passEvent( event, this );
								break;

							case esystemevent.msbutton:
								mouseState.Keys[ event.ints["ms_button"] ] = event.bools["ms_pressed"];
								generateEvent( event );
								break;

							case esystemevent.msmove:
								mouseState.x += event.ints[ "ms_x"];
								mouseState.y += event.ints[ "ms_y" ];
								generateEvent( event );
								break;

							case esystemevent.mswheel:
								mouseState.Wheel += event.ints["ms_wheel"];
								generateEvent( event );
								break;

							default: break;
						}

				default: break;		// here we do not transfer unhandled events to our listeners, for now...
			}
		}


		debug void DbgReport()
		{
			debugLogY( "================" );
			debugLogY( "Moonglide engine. DEBUG REPORT" );
			debugLogY( "File: <" ~ __FILE__ ~ ">" );

			debugLog.trace("Check subsystems..");

			if (_renderer !is null)
				_renderer.DbgReport();
			else
				debugLogR( "Renderer is null!" );

			if (_scenemanager !is null)
				_scenemanager.DbgReport();
			else
				debugLogR( "scenemanager is null!" );

			if (_resmanager !is null)
				_resmanager.DbgReport();
			else
				debugLogR( "resmanager is null!" );

			if (_uploadmanager !is null)
				_uploadmanager.DbgReport();
			else
				debugLogR( "uploadmanager is null!" );

			debugLogY( "Moonglide engine. END OF DEBUG REPORT" );
			debugLogY( "================" );
		}
}
